/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ghidra.pcode.emu.jit.gen.tgt;

import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.objectweb.asm.Opcodes;

import ghidra.pcode.emu.jit.JitCompiler;
import ghidra.pcode.emu.jit.JitPassage.*;
import ghidra.pcode.emu.jit.JitPcodeThread;
import ghidra.pcode.emu.jit.analysis.JitDataFlowUseropLibrary;
import ghidra.pcode.emu.jit.analysis.JitType.IntJitType;
import ghidra.pcode.emu.jit.analysis.JitType.LongJitType;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.lang.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.util.DefaultLanguageService;

/**
 * The interface implemented by classfiles generated by {@link JitCompiler}.
 * 
 * <p>
 * This also serves as a run-time library of routines that implement p-code ops not trivially
 * implemented by the JVM or its run-time library. In theory, they can be inlined by the JVM's JIT
 * at its discretion.
 */
public interface JitCompiledPassage {

	/**
	 * An entry point that is not yet bound to a specific thread
	 * 
	 * @implNote This would be a {@code record} except that it maintains the cache of instances per
	 *           thread
	 */
	class EntryPointPrototype {
		private final JitCompiledPassageClass cls;
		private final int blockId;
		private final Map<JitPcodeThread, EntryPoint> perThread = new HashMap<>();

		/**
		 * Construct an entry prototype
		 * 
		 * @param cls the compiled passage class (i.e., passage not yet bound to a specific thread)
		 * @param blockId the block at which to enter the passage
		 */
		public EntryPointPrototype(JitCompiledPassageClass cls, int blockId) {
			this.cls = cls;
			this.blockId = blockId;
		}

		@Override
		public String toString() {
			return "EntryPointPrototype[%s,%d]".formatted(cls, blockId);
		}

		@Override
		public boolean equals(Object obj) {
			if (!(obj instanceof EntryPointPrototype that)) {
				return false;
			}
			if (!this.cls.equals(that.cls)) {
				return false;
			}
			if (this.blockId != that.blockId) {
				return false;
			}
			return true;
		}

		@Override
		public int hashCode() {
			return Objects.hash(cls, blockId);
		}

		/**
		 * Create (or get) the entry point for the given thread
		 * 
		 * @param thread the thread to bind to the entry point
		 * @return the resulting entry point
		 * @see JitPcodeThread#getEntry(AddrCtx)
		 */
		public EntryPoint createInstance(JitPcodeThread thread) {
			synchronized (perThread) {
				return perThread.computeIfAbsent(thread,
					t -> new EntryPoint(this, cls.createInstance(t), blockId));
			}
		}
	}

	/**
	 * An entry point into a translated passage
	 * 
	 * <p>
	 * This represents a translated passage and an index into its list of entry points. For an
	 * overview of how this fits into the JIT-accelerated execution loop, see
	 * {@link JitPcodeThread}, especially the <b>Translate</b> and <b>Execute</b> sections. For
	 * details of how the entry points and their metadata are exported, see
	 * {@link JitCodeGenerator}, especially the <b>Entry Point Dispatch</b> section.
	 * 
	 * @param prototype the entry point prototype (passage class and blockId without bound thread)
	 * @param passage the compiled passage, instantiated for the bound thread
	 * @param blockId an index identifying the block at the target address and contextreg value of
	 *            this entry point
	 */
	record EntryPoint(EntryPointPrototype prototype, JitCompiledPassage passage, int blockId) {
		/**
		 * Start/resume execution of the bound thread at this entry point.
		 * 
		 * <p>
		 * The associated passage is invoked, starting at the given block via
		 * {@link JitCompiledPassage#run(int)}, which was generated by {@link JitCodeGenerator}.
		 * 
		 * @return as in {@link JitCompiledPassage#run(int)}
		 */
		public EntryPoint run() {
			return passage.run(blockId);
		}
	}

	/**
	 * A cache slot for a chained entry point
	 * 
	 * <p>
	 * One of these is constructed for each target of a direct branch that exits the passage,
	 * including those of synthetic {@link ExitPcodeOp exit} ops. For each such branch, the
	 * {@link JitCodeGenerator} emits code to invoke {@link #getChained()} on the target's exit slot
	 * and to return the resulting entry point.
	 */
	public static class ExitSlot {
		private final JitPcodeThread thread;
		private final AddrCtx pcCtx;

		private EntryPoint chained;

		/**
		 * Construct an exit slot for the given target and bound thread
		 * 
		 * @param thread the bound thread for the passage constructing this slot
		 * @param target the offset of the target address
		 * @param ctx the target decode context
		 */
		public ExitSlot(JitPcodeThread thread, long target, RegisterValue ctx) {
			this.thread = thread;
			this.pcCtx =
				new AddrCtx(ctx, thread.getLanguage().getDefaultSpace().getAddress(target));
		}

		/**
		 * Get the entry point for this target
		 * 
		 * <p>
		 * This may cause the emulator to translate a new passage.
		 * 
		 * @return the entry point
		 * @implNote This will always return a non-null entry point, even if the branch target is
		 *           invalid. In that case, the "passage" will consist of a single
		 *           {@link DecodeErrorInstruction}, which will ensure the emulator crashes upon
		 *           trying to execute at the target address.
		 */
		public EntryPoint getChained() {
			if (chained == null) {
				chained = computeChained();
			}
			return chained;
		}

		private EntryPoint computeChained() {
			return thread.getEntry(pcCtx);
		}
	}

	/**
	 * Run the compiled passage of code
	 * 
	 * <p>
	 * Except during testing, this is ordinarily called by {@link EntryPoint#run()}. Too see how
	 * this fits into the overall JIT-accelerated execution loop, see {@link JitPcodeThread}. All
	 * implementations of this interface are generated dynamically. To understand that process and
	 * how the entry points are generated and exported, see {@link JitCompiler}.
	 * 
	 * <p>
	 * This method may or may not return a chained entry point. Each passage caches a chained entry
	 * point for each of its direct branch targets. This averts a map lookup on subsequent exits via
	 * the same branch. If a chained entry point is returned, the thread ought to execute it
	 * immediately, unless it has become suspended. Otherwise, the thread must repeat its execution
	 * loop at the <b>Fetch</b> step.
	 * 
	 * @param blockId an index identifying the target address and contextreg where execution should
	 *            enter
	 * @return a chained entry point, or {@code null}
	 */
	EntryPoint run(int blockId);

	/**
	 * Read an {@link IntJitType#I1 int1} from the given array at the given offset
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM int
	 */
	static int readInt1(byte[] arr, int offset) {
		return Byte.toUnsignedInt(arr[offset]);
	}

	/**
	 * Read an {@link IntJitType#I2 int2} from the given array at the given offset in big endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM int
	 */
	static int readIntBE2(byte[] arr, int offset) {
		return Byte.toUnsignedInt(arr[offset]) << 8 |
			Byte.toUnsignedInt(arr[offset + 1]);
	}

	/**
	 * Read an {@link IntJitType#I3 int3} from the given array at the given offset in big endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM int
	 */
	static int readIntBE3(byte[] arr, int offset) {
		return Byte.toUnsignedInt(arr[offset]) << 16 |
			Byte.toUnsignedInt(arr[offset + 1]) << 8 |
			Byte.toUnsignedInt(arr[offset + 2]);
	}

	/**
	 * Read an {@link IntJitType#I4 int4} from the given array at the given offset in big endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM int
	 */
	static int readIntBE4(byte[] arr, int offset) {
		return Byte.toUnsignedInt(arr[offset]) << 24 |
			Byte.toUnsignedInt(arr[offset + 1]) << 16 |
			Byte.toUnsignedInt(arr[offset + 2]) << 8 |
			Byte.toUnsignedInt(arr[offset + 3]);
	}

	/**
	 * Read an {@link IntJitType#I2 int2} from the given array at the given offset in little endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM int
	 */
	static int readIntLE2(byte[] arr, int offset) {
		return Byte.toUnsignedInt(arr[offset]) |
			Byte.toUnsignedInt(arr[offset + 1]) << 8;
	}

	/**
	 * Read an {@link IntJitType#I3 int3} from the given array at the given offset in little endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM int
	 */
	static int readIntLE3(byte[] arr, int offset) {
		return Byte.toUnsignedInt(arr[offset]) |
			Byte.toUnsignedInt(arr[offset + 1]) << 8 |
			Byte.toUnsignedInt(arr[offset + 2]) << 16;
	}

	/**
	 * Read an {@link IntJitType#I4 int4} from the given array at the given offset in little endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM int
	 */
	static int readIntLE4(byte[] arr, int offset) {
		return Byte.toUnsignedInt(arr[offset]) |
			Byte.toUnsignedInt(arr[offset + 1]) << 8 |
			Byte.toUnsignedInt(arr[offset + 2]) << 16 |
			Byte.toUnsignedInt(arr[offset + 3]) << 24;
	}

	/**
	 * Write an {@link IntJitType#I1 int1} into the given array at the given offset
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM int
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeInt1(int value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
	}

	/**
	 * Write an {@link IntJitType#I2 int2} into the given array at the given offset in big endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM int
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeIntBE2(int value, byte[] arr, int offset) {
		arr[offset] = (byte) (value >> 8);
		arr[offset + 1] = (byte) value;
	}

	/**
	 * Write an {@link IntJitType#I3 int3} into the given array at the given offset in big endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM int
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeIntBE3(int value, byte[] arr, int offset) {
		arr[offset] = (byte) (value >> 16);
		arr[offset + 1] = (byte) (value >> 8);
		arr[offset + 2] = (byte) value;
	}

	/**
	 * Write an {@link IntJitType#I4 int4} into the given array at the given offset in big endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM int
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeIntBE4(int value, byte[] arr, int offset) {
		arr[offset] = (byte) (value >> 24);
		arr[offset + 1] = (byte) (value >> 16);
		arr[offset + 2] = (byte) (value >> 8);
		arr[offset + 3] = (byte) value;
	}

	/**
	 * Write an {@link IntJitType#I2 int2} into the given array at the given offset in litte endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM int
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeIntLE2(int value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
		arr[offset + 1] = (byte) (value >> 8);
	}

	/**
	 * Write an {@link IntJitType#I3 int3} into the given array at the given offset in litte endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM int
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeIntLE3(int value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
		arr[offset + 1] = (byte) (value >> 8);
		arr[offset + 2] = (byte) (value >> 16);
	}

	/**
	 * Write an {@link IntJitType#I4 int4} into the given array at the given offset in litte endian
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM int
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeIntLE4(int value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
		arr[offset + 1] = (byte) (value >> 8);
		arr[offset + 2] = (byte) (value >> 16);
		arr[offset + 3] = (byte) (value >> 24);
	}

	/**
	 * Read an {@link IntJitType#I1 int1} from the given array at the given offset.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I1 int1} can fit in
	 * a JVM int, this method is used when reading 1 byte of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLong1(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]);
	}

	/**
	 * Read an {@link IntJitType#I2 int2} from the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I2 int2} can fit in
	 * a JVM int, this method is used when reading 2 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongBE2(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) << 8 |
			Byte.toUnsignedLong(arr[offset + 1]);
	}

	/**
	 * Read an {@link IntJitType#I3 int3} from the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I3 int3} can fit in
	 * a JVM int, this method is used when reading 3 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongBE3(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) << 16 |
			Byte.toUnsignedLong(arr[offset + 1]) << 8 |
			Byte.toUnsignedLong(arr[offset + 2]);
	}

	/**
	 * Read an {@link IntJitType#I4 int4} from the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I4 int4} can fit in
	 * a JVM int, this method is used when reading 4 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongBE4(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) << 24 |
			Byte.toUnsignedLong(arr[offset + 1]) << 16 |
			Byte.toUnsignedLong(arr[offset + 2]) << 8 |
			Byte.toUnsignedLong(arr[offset + 3]);
	}

	/**
	 * Read an {@link LongJitType#I5 int5} from the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongBE5(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) << 32 |
			Byte.toUnsignedLong(arr[offset + 1]) << 24 |
			Byte.toUnsignedLong(arr[offset + 2]) << 16 |
			Byte.toUnsignedLong(arr[offset + 3]) << 8 |
			Byte.toUnsignedLong(arr[offset + 4]);
	}

	/**
	 * Read an {@link LongJitType#I6 int6} from the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongBE6(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) << 40 |
			Byte.toUnsignedLong(arr[offset + 1]) << 32 |
			Byte.toUnsignedLong(arr[offset + 2]) << 24 |
			Byte.toUnsignedLong(arr[offset + 3]) << 16 |
			Byte.toUnsignedLong(arr[offset + 4]) << 8 |
			Byte.toUnsignedLong(arr[offset + 5]);
	}

	/**
	 * Read an {@link LongJitType#I7 int7} from the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongBE7(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) << 48 |
			Byte.toUnsignedLong(arr[offset + 1]) << 40 |
			Byte.toUnsignedLong(arr[offset + 2]) << 32 |
			Byte.toUnsignedLong(arr[offset + 3]) << 24 |
			Byte.toUnsignedLong(arr[offset + 4]) << 16 |
			Byte.toUnsignedLong(arr[offset + 5]) << 8 |
			Byte.toUnsignedLong(arr[offset + 6]);
	}

	/**
	 * Read an {@link LongJitType#I8 int8} from the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongBE8(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) << 56 |
			Byte.toUnsignedLong(arr[offset + 1]) << 48 |
			Byte.toUnsignedLong(arr[offset + 2]) << 40 |
			Byte.toUnsignedLong(arr[offset + 3]) << 32 |
			Byte.toUnsignedLong(arr[offset + 4]) << 24 |
			Byte.toUnsignedLong(arr[offset + 5]) << 16 |
			Byte.toUnsignedLong(arr[offset + 6]) << 8 |
			Byte.toUnsignedLong(arr[offset + 7]);
	}

	/**
	 * Read an {@link IntJitType#I2 int2} from the given array at the given offset in little endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I2 int2} can fit in
	 * a JVM int, this method is used when reading 2 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongLE2(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) |
			Byte.toUnsignedLong(arr[offset + 1]) << 8;
	}

	/**
	 * Read an {@link IntJitType#I3 int3} from the given array at the given offset in little endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I3 int3} can fit in
	 * a JVM int, this method is used when reading 3 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongLE3(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) |
			Byte.toUnsignedLong(arr[offset + 1]) << 8 |
			Byte.toUnsignedLong(arr[offset + 2]) << 16;
	}

	/**
	 * Read an {@link IntJitType#I4 int4} from the given array at the given offset in little endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I4 int4} can fit in
	 * a JVM int, this method is used when reading 4 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongLE4(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) |
			Byte.toUnsignedLong(arr[offset + 1]) << 8 |
			Byte.toUnsignedLong(arr[offset + 2]) << 16 |
			Byte.toUnsignedLong(arr[offset + 3]) << 24;
	}

	/**
	 * Read an {@link LongJitType#I5 int5} from the given array at the given offset in little
	 * endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongLE5(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) |
			Byte.toUnsignedLong(arr[offset + 1]) << 8 |
			Byte.toUnsignedLong(arr[offset + 2]) << 16 |
			Byte.toUnsignedLong(arr[offset + 3]) << 24 |
			Byte.toUnsignedLong(arr[offset + 4]) << 32;
	}

	/**
	 * Read an {@link LongJitType#I6 int6} from the given array at the given offset in little
	 * endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongLE6(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) |
			Byte.toUnsignedLong(arr[offset + 1]) << 8 |
			Byte.toUnsignedLong(arr[offset + 2]) << 16 |
			Byte.toUnsignedLong(arr[offset + 3]) << 24 |
			Byte.toUnsignedLong(arr[offset + 4]) << 32 |
			Byte.toUnsignedLong(arr[offset + 5]) << 40;
	}

	/**
	 * Read an {@link LongJitType#I7 int7} from the given array at the given offset in little
	 * endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongLE7(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) |
			Byte.toUnsignedLong(arr[offset + 1]) << 8 |
			Byte.toUnsignedLong(arr[offset + 2]) << 16 |
			Byte.toUnsignedLong(arr[offset + 3]) << 24 |
			Byte.toUnsignedLong(arr[offset + 4]) << 32 |
			Byte.toUnsignedLong(arr[offset + 5]) << 40 |
			Byte.toUnsignedLong(arr[offset + 6]) << 48;
	}

	/**
	 * Read an {@link LongJitType#I8 int8} from the given array at the given offset in little
	 * endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param arr the array
	 * @param offset the offset
	 * @return the value as a JVM long
	 */
	static long readLongLE8(byte[] arr, int offset) {
		return Byte.toUnsignedLong(arr[offset]) |
			Byte.toUnsignedLong(arr[offset + 1]) << 8 |
			Byte.toUnsignedLong(arr[offset + 2]) << 16 |
			Byte.toUnsignedLong(arr[offset + 3]) << 24 |
			Byte.toUnsignedLong(arr[offset + 4]) << 32 |
			Byte.toUnsignedLong(arr[offset + 5]) << 40 |
			Byte.toUnsignedLong(arr[offset + 6]) << 48 |
			Byte.toUnsignedLong(arr[offset + 7]) << 56;
	}

	/**
	 * Write an {@link IntJitType#I1 int1} into the given array at the given offset.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I1 int1} can fit in
	 * a JVM int, this method is used when writing 1 byte of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLong1(long value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
	}

	/**
	 * Write an {@link IntJitType#I2 int2} into the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I2 int2} can fit in
	 * a JVM int, this method is used when writing 2 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongBE2(long value, byte[] arr, int offset) {
		arr[offset] = (byte) (value >> 8);
		arr[offset + 1] = (byte) value;
	}

	/**
	 * Write an {@link IntJitType#I3 int3} into the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I3 int3} can fit in
	 * a JVM int, this method is used when writing 3 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongBE3(long value, byte[] arr, int offset) {
		arr[offset] = (byte) (value >> 16);
		arr[offset + 1] = (byte) (value >> 8);
		arr[offset + 2] = (byte) value;
	}

	/**
	 * Write an {@link IntJitType#I4 int4} into the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I4 int4} can fit in
	 * a JVM int, this method is used when writing 4 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongBE4(long value, byte[] arr, int offset) {
		arr[offset] = (byte) (value >> 24);
		arr[offset + 1] = (byte) (value >> 16);
		arr[offset + 2] = (byte) (value >> 8);
		arr[offset + 3] = (byte) value;
	}

	/**
	 * Write an {@link LongJitType#I5 int5} into the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongBE5(long value, byte[] arr, int offset) {
		arr[offset] = (byte) (value >> 32);
		arr[offset + 1] = (byte) (value >> 24);
		arr[offset + 2] = (byte) (value >> 16);
		arr[offset + 3] = (byte) (value >> 8);
		arr[offset + 4] = (byte) value;
	}

	/**
	 * Write an {@link LongJitType#I6 int6} into the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongBE6(long value, byte[] arr, int offset) {
		arr[offset] = (byte) (value >> 40);
		arr[offset + 1] = (byte) (value >> 32);
		arr[offset + 2] = (byte) (value >> 24);
		arr[offset + 3] = (byte) (value >> 16);
		arr[offset + 4] = (byte) (value >> 8);
		arr[offset + 5] = (byte) value;
	}

	/**
	 * Write an {@link LongJitType#I7 int7} into the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongBE7(long value, byte[] arr, int offset) {
		arr[offset] = (byte) (value >> 48);
		arr[offset + 1] = (byte) (value >> 40);
		arr[offset + 2] = (byte) (value >> 32);
		arr[offset + 3] = (byte) (value >> 24);
		arr[offset + 4] = (byte) (value >> 16);
		arr[offset + 5] = (byte) (value >> 8);
		arr[offset + 6] = (byte) value;
	}

	/**
	 * Write an {@link LongJitType#I8 int8} into the given array at the given offset in big endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongBE8(long value, byte[] arr, int offset) {
		arr[offset] = (byte) (value >> 56);
		arr[offset + 1] = (byte) (value >> 48);
		arr[offset + 2] = (byte) (value >> 40);
		arr[offset + 3] = (byte) (value >> 32);
		arr[offset + 4] = (byte) (value >> 24);
		arr[offset + 5] = (byte) (value >> 16);
		arr[offset + 6] = (byte) (value >> 8);
		arr[offset + 7] = (byte) value;
	}

	/**
	 * Write an {@link IntJitType#I2 int2} into the given array at the given offset in little
	 * endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I2 int2} can fit in
	 * a JVM int, this method is used when writing 2 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongLE2(long value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
		arr[offset + 1] = (byte) (value >> 8);
	}

	/**
	 * Write an {@link IntJitType#I3 int3} into the given array at the given offset in little
	 * endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I3 int3} can fit in
	 * a JVM int, this method is used when writing 3 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongLE3(long value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
		arr[offset + 1] = (byte) (value >> 8);
		arr[offset + 2] = (byte) (value >> 16);
	}

	/**
	 * Write an {@link IntJitType#I4 int4} into the given array at the given offset in little
	 * endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code. While an {@link IntJitType#I4 int4} can fit in
	 * a JVM int, this method is used when writing 4 bytes of a {@link LongJitType larger int} that
	 * spans a page boundary.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongLE4(long value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
		arr[offset + 1] = (byte) (value >> 8);
		arr[offset + 2] = (byte) (value >> 16);
		arr[offset + 3] = (byte) (value >> 24);
	}

	/**
	 * Write an {@link LongJitType#I5 int5} into the given array at the given offset in little
	 * endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongLE5(long value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
		arr[offset + 1] = (byte) (value >> 8);
		arr[offset + 2] = (byte) (value >> 16);
		arr[offset + 3] = (byte) (value >> 24);
		arr[offset + 4] = (byte) (value >> 32);
	}

	/**
	 * Write an {@link LongJitType#I6 int6} into the given array at the given offset in little
	 * endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongLE6(long value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
		arr[offset + 1] = (byte) (value >> 8);
		arr[offset + 2] = (byte) (value >> 16);
		arr[offset + 3] = (byte) (value >> 24);
		arr[offset + 4] = (byte) (value >> 32);
		arr[offset + 5] = (byte) (value >> 40);
	}

	/**
	 * Write an {@link LongJitType#I7 int7} into the given array at the given offset in little
	 * endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongLE7(long value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
		arr[offset + 1] = (byte) (value >> 8);
		arr[offset + 2] = (byte) (value >> 16);
		arr[offset + 3] = (byte) (value >> 24);
		arr[offset + 4] = (byte) (value >> 32);
		arr[offset + 5] = (byte) (value >> 40);
		arr[offset + 6] = (byte) (value >> 48);
	}

	/**
	 * Write an {@link LongJitType#I8 int8} into the given array at the given offset in little
	 * endian.
	 * 
	 * <p>
	 * This is invoked by dynamically generated code.
	 * 
	 * @param value the value as a JVM long
	 * @param arr the array
	 * @param offset the offset
	 */
	static void writeLongLE8(long value, byte[] arr, int offset) {
		arr[offset] = (byte) value;
		arr[offset + 1] = (byte) (value >> 8);
		arr[offset + 2] = (byte) (value >> 16);
		arr[offset + 3] = (byte) (value >> 24);
		arr[offset + 4] = (byte) (value >> 32);
		arr[offset + 5] = (byte) (value >> 40);
		arr[offset + 6] = (byte) (value >> 48);
		arr[offset + 7] = (byte) (value >> 56);
	}

	/**
	 * Convert two integers into a single long
	 * 
	 * <p>
	 * In terms of the JVM stack, this simply converts the top two ints to an equivalent long.
	 * <b>TODO</b>: This no longer appears to be used, but may be in anticipation of multi-precision
	 * integer support.
	 * 
	 * @param msl the more significant leg
	 * @param lsl the less significant leg
	 * @return the long
	 */
	static long conv2IntToLong(int msl, int lsl) {
		return Integer.toUnsignedLong(msl) << Integer.SIZE | Integer.toUnsignedLong(lsl);
	}

	/**
	 * The implementation of {@link PcodeOp#INT_SBORROW int_sborrow} on JVM ints.
	 * 
	 * <p>
	 * This actually computes all the borrow bits. To extract a specific one, the generator should
	 * emit a shift and mask.
	 * 
	 * @param a the first operand as in {@code a - b}
	 * @param b the second operand as in {@code a - b}
	 * @return the register of borrow bits
	 */
	static int sBorrowIntRaw(int a, int b) {
		int r = a - b;
		a ^= r;
		r ^= b;
		r ^= -1;
		a &= r;
		return a;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_SBORROW int_sborrow} on JVM longs.
	 * 
	 * <p>
	 * This actually computes all the borrow bits. To extract a specific one, the generator should
	 * emit a shift and mask.
	 * 
	 * @param a the first operand as in {@code a - b}
	 * @param b the second operand as in {@code a - b}
	 * @return the register of borrow bits
	 */
	static long sBorrowLongRaw(long a, long b) {
		long r = a - b;
		a ^= r;
		r ^= b;
		r ^= -1;
		a &= r;
		return a;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_SCARRY int_sborrow} on multi-precision ints.
	 * 
	 * @param a the first operand as in {@code a - b}
	 * @param b the second operand as in {@code a - b}
	 * @param shift one less than the number of bits in each most-significant leg, i.e., the number
	 *            of bits to shift right such that the most-significant bit of the most-significant
	 *            leg becomes the least-significant bit of the <em>most</em>-significant leg.
	 * @return the one carry bit
	 */
	static int sBorrowMpInt(int[] a, int[] b, int shift) {
		assert a.length == b.length;
		long carry = 0;
		for (int i = 0; i < a.length; i++) {
			carry >>= Integer.SIZE;
			carry += (a[i] & MASK_I2UL) - (b[i] & MASK_I2UL);
		}
		int msr = (int) carry;
		int msa = a[a.length - 1];
		int msb = b[b.length - 1];

		msa ^= msr;
		msr ^= msb;
		msr ^= -1;
		msa &= msr;

		return (msa >> shift) & 1;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_SCARRY int_scarry} on JVM ints.
	 * 
	 * <p>
	 * This actually computes all the carry bits. To extract a specific one, the generator should
	 * emit a shift and mask.
	 * 
	 * @param a the first operand as in {@code a + b}
	 * @param b the second operand as in {@code a + b}
	 * @return the register of carry bits
	 */
	static int sCarryIntRaw(int a, int b) {
		int r = a + b;
		r ^= a;
		a ^= b;
		a ^= -1;
		r &= a;
		return r;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_SCARRY int_scarry} on JVM longs.
	 * 
	 * <p>
	 * This actually computes all the carry bits. To extract a specific one, the generator should
	 * emit a shift and mask.
	 * 
	 * @param a the first operand as in {@code a + b}
	 * @param b the second operand as in {@code a + b}
	 * @return the register of carry bits
	 */
	static long sCarryLongRaw(long a, long b) {
		long r = a + b;
		r ^= a;
		a ^= b;
		a ^= -1;
		r &= a;
		return r;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_SCARRY int_scarry} on multi-precision ints.
	 * 
	 * @param a the first operand as in {@code a + b}
	 * @param b the second operand as in {@code a + b}
	 * @param shift one less than the number of bits in each most-significant leg, i.e., the number
	 *            of bits to shift right such that the most-significant bit of the most-significant
	 *            leg becomes the least-significant bit of the <em>most</em>-significant leg.
	 * @return the one carry bit
	 */
	static int sCarryMpInt(int[] a, int[] b, int shift) {
		assert a.length == b.length;
		long carry = 0;
		for (int i = 0; i < a.length; i++) {
			carry >>>= Integer.SIZE;
			carry += (a[i] & MASK_I2UL) + (b[i] & MASK_I2UL);
		}
		int msr = (int) carry;
		int msa = a[a.length - 1];
		int msb = b[b.length - 1];

		msr ^= msa;
		msa ^= msb;
		msa ^= -1;
		msr &= msa;

		return (msr >> shift) & 1;
	}

	enum MpShiftPrivate {
		;
		static void shl(int[] out, int[] val, int amt) {
			int legs = amt >>> 5;
			int bits = amt & 0x1f;
			/*for (int i = 0; i < out.length && i < legs; i++) {
				out[i] = 0;
			}*/
			if (bits == 0) {
				for (int i = 0; i < val.length - legs & i < out.length - legs; i++) {
					out[i + legs] = val[i];
				}
				return;
			}
			int prev = 0;
			for (int i = 0; i < val.length - legs & i < out.length - legs; i++) {
				out[i + legs] = (val[i] << bits) | (prev >>> (Integer.SIZE - bits));
				prev = val[i];
			}
		}

		static void ushr(int[] out, int[] val, int amt) {
			int legs = amt >>> 5;
			int bits = amt & 0x1f;
			/*for (int i = 0; i < out.length && i < legs; i++) {
				out[i + legs] = 0;
			}*/
			if (bits == 0) {
				for (int i = 0; i < val.length - legs & i < out.length; i++) {
					out[i] = val[i + legs];
				}
				return;
			}
			int prev = 0;
			for (int i = Math.min(val.length - legs, out.length) - 1; i >= 0; i--) {
				out[i] = (val[i + legs] >>> bits) | (prev << (Integer.SIZE - bits));
				prev = val[i + legs];
			}
		}

		static void sshr(int[] out, int[] val, int amt, int sign) {
			int legs = amt >>> 5;
			int bits = amt & 0x1f;
			if (bits == 0) {
				for (int i = 0; i < val.length - legs & i < out.length; i++) {
					out[i] = val[i + legs];
				}
				if (sign != 0) {
					for (int i = val.length - legs; i < out.length; i++) {
						out[i] = sign;
					}
				}
				return;
			}
			int prev = 0;
			// Only apply signed shift to most-significant leg of val
			if (val.length - legs - 1 >= 0) {
				out[val.length - legs - 1] = (val[val.length - 1] >> bits);
				prev = val[val.length - 1];
			}
			for (int i = Math.min(val.length - legs, out.length) - 2; i >= 0; i--) {
				out[i] = (val[i + legs] >>> bits) | (prev << (Integer.SIZE - bits));
				prev = val[i + legs];
			}
			if (sign != 0) {
				for (int i = val.length - legs; i < out.length; i++) {
					out[i] = sign;
				}
			}
		}
	}

	/**
	 * The implementation of {@link PcodeOp#INT_LEFT int_left} on multi-precision ints.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param out the array to receive the output, in little-endian order
	 * @param outBytes the actual size in bytes of the output operand
	 * @param val the value as in {@code val << amt}, in little-endian order
	 * @param amt the amt as in {@code val << amt}, in little-endian order
	 */
	static void intLeft(int[] out, int outBytes, int[] val, int[] amt) {
		if (Integer.compareUnsigned(amt[0], outBytes * Byte.SIZE) >= 0) {
			Arrays.fill(out, 0);
			return;
		}
		for (int i = 1; i < amt.length; i++) {
			if (amt[i] != 0) {
				Arrays.fill(out, 0);
				return;
			}
		}
		MpShiftPrivate.shl(out, val, amt[0]);
	}

	/**
	 * The implementation of {@link PcodeOp#INT_LEFT int_left} on an mp-int with a JVM long shift
	 * amount.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param out the array to receive the output, in little-endian order
	 * @param outBytes the actual size in bytes of the output operand
	 * @param val the value as in {@code val << amt}, in little-endian order
	 * @param amt the amt as in {@code val << amt}
	 */
	static void intLeft(int[] out, int outBytes, int[] val, long amt) {
		if (Long.compareUnsigned(amt, (outBytes & MASK_I2UL) * Byte.SIZE) >= 0) {
			Arrays.fill(out, 0);
			return;
		}
		MpShiftPrivate.shl(out, val, (int) amt);
	}

	/**
	 * The implementation of {@link PcodeOp#INT_LEFT int_left} on an mp-int with a JVM int shift
	 * amount.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param out the array to receive the output, in little-endian order
	 * @param outBytes the actual size in bytes of the output operand
	 * @param val the value as in {@code val << amt}, in little-endian order
	 * @param amt the amt as in {@code val << amt}
	 */
	static void intLeft(int[] out, int outBytes, int[] val, int amt) {
		if (Integer.compareUnsigned(amt, outBytes * Byte.SIZE) >= 0) {
			Arrays.fill(out, 0);
			return;
		}
		MpShiftPrivate.shl(out, val, amt);
	}

	/**
	 * The implementation of {@link PcodeOp#INT_LEFT int_left} on a JVM long with an mp-int shift
	 * amount.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val << amt}
	 * @param amt the amt as in {@code val << amt}, in little-endian order
	 * @return the value
	 */
	static long intLeft(long val, int[] amt) {
		if (Long.compareUnsigned(Integer.toUnsignedLong(amt[0]), Long.SIZE) >= 0) {
			return 0;
		}
		for (int i = 1; i < amt.length; i++) {
			if (amt[i] != 0) {
				return 0;
			}
		}
		return val << amt[0];
	}

	/**
	 * The implementation of {@link PcodeOp#INT_LEFT int_left} on JVM longs.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val << amt}
	 * @param amt the amt as in {@code val << amt}
	 * @return the value
	 */
	static long intLeft(long val, long amt) {
		if (Long.compareUnsigned(amt, Long.SIZE) >= 0) {
			return 0;
		}
		return val << amt;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_LEFT int_left} on JVM long with int amt.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val << amt}
	 * @param amt the amt as in {@code val << amt}
	 * @return the value
	 */
	static long intLeft(long val, int amt) {
		if (Integer.compareUnsigned(amt, Long.SIZE) >= 0) {
			return 0;
		}
		return val << amt;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_LEFT int_left} on a JVM int with an mp-int shift
	 * amount.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val << amt}
	 * @param amt the amt as in {@code val << amt}, in little-endian order
	 * @return the value
	 */
	static long intLeft(int val, int[] amt) {
		if (Integer.compareUnsigned(amt[0], Integer.SIZE) >= 0) {
			return 0;
		}
		for (int i = 1; i < amt.length; i++) {
			if (amt[i] != 0) {
				return 0;
			}
		}
		return val << amt[0];
	}

	/**
	 * The implementation of {@link PcodeOp#INT_LEFT int_left} on JVM int with long amt.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val << amt}
	 * @param amt the amt as in {@code val << amt}
	 * @return the value
	 */
	static int intLeft(int val, long amt) {
		if (Long.compareUnsigned(amt, Integer.SIZE) >= 0) {
			return 0;
		}
		return val << amt;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_LEFT int_left} on JVM ints.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val << amt}
	 * @param amt the amt as in {@code val << amt}
	 * @return the value
	 */
	static int intLeft(int val, int amt) {
		if (Long.compareUnsigned(amt, Integer.SIZE) >= 0) {
			return 0;
		}
		return val << amt;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_RIGHT int_right} on multi-precision ints.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param out the array to receive the output, in little-endian order
	 * @param outBytes the actual size in bytes of the output operand
	 * @param val the value as in {@code val >> amt}, in little-endian order
	 * @param amt the amt as in {@code val >> amt}, in little-endian order
	 */
	static void intRight(int[] out, int outBytes, int[] val, int[] amt) {
		if (Integer.compareUnsigned(amt[0], outBytes * Byte.SIZE) >= 0) {
			Arrays.fill(out, 0);
			return;
		}
		for (int i = 1; i < amt.length; i++) {
			if (amt[i] != 0) {
				Arrays.fill(out, 0);
				return;
			}
		}
		MpShiftPrivate.ushr(out, val, amt[0]);
	}

	/**
	 * The implementation of {@link PcodeOp#INT_RIGHT int_right} on an mp-int with a JVM long shift
	 * amount.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param out the array to receive the output, in little-endian order
	 * @param outBytes the actual size in bytes of the output operand
	 * @param val the value as in {@code val >> amt}, in little-endian order
	 * @param amt the amt as in {@code val >> amt}
	 */
	static void intRight(int[] out, int outBytes, int[] val, long amt) {
		if (Long.compareUnsigned(amt, (outBytes & MASK_I2UL) * Byte.SIZE) >= 0) {
			Arrays.fill(out, 0);
			return;
		}
		MpShiftPrivate.ushr(out, val, (int) amt);
	}

	/**
	 * The implementation of {@link PcodeOp#INT_RIGHT int_right} on an mp-int with a JVM int shift
	 * amount.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param out the array to receive the output, in little-endian order
	 * @param outBytes the actual size in bytes of the output operand
	 * @param val the value as in {@code val >> amt}, in little-endian order
	 * @param amt the amt as in {@code val >> amt}
	 */
	static void intRight(int[] out, int outBytes, int[] val, int amt) {
		if (Integer.compareUnsigned(amt, outBytes * Byte.SIZE) >= 0) {
			Arrays.fill(out, 0);
			return;
		}
		MpShiftPrivate.ushr(out, val, amt);
	}

	/**
	 * The implementation of {@link PcodeOp#INT_RIGHT int_right} on a JVM long with an mp-int shift
	 * amount.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val >> amt}
	 * @param amt the amt as in {@code val >> amt}, in little-endian order
	 * @return the value
	 */
	static long intRight(long val, int[] amt) {
		if (Long.compareUnsigned(Integer.toUnsignedLong(amt[0]), Long.SIZE) >= 0) {
			return 0;
		}
		for (int i = 1; i < amt.length; i++) {
			if (amt[i] != 0) {
				return 0;
			}
		}
		return val >>> amt[0];
	}

	/**
	 * The implementation of {@link PcodeOp#INT_RIGHT int_right} on JVM longs.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val >> amt}
	 * @param amt the amt as in {@code val >> amt}
	 * @return the value
	 */
	static long intRight(long val, long amt) {
		if (Long.compareUnsigned(amt, Long.SIZE) >= 0) {
			return 0;
		}
		return val >>> amt;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_RIGHT int_right} on JVM long with int amt.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val >> amt}
	 * @param amt the amt as in {@code val >> amt}
	 * @return the value
	 */
	static long intRight(long val, int amt) {
		if (Integer.compareUnsigned(amt, Long.SIZE) >= 0) {
			return 0;
		}
		return val >>> amt;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_RIGHT int_right} on a JVM int with an mp-int shift
	 * amount.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val >> amt}
	 * @param amt the amt as in {@code val >> amt}, in little-endian order
	 * @return the value
	 */
	static long intRight(int val, int[] amt) {
		if (Integer.compareUnsigned(amt[0], Integer.SIZE) >= 0) {
			return 0;
		}
		for (int i = 1; i < amt.length; i++) {
			if (amt[i] != 0) {
				return 0;
			}
		}
		return val >>> amt[0];
	}

	/**
	 * The implementation of {@link PcodeOp#INT_RIGHT int_right} on JVM int with long amt.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val >> amt}
	 * @param amt the amt as in {@code val >> amt}
	 * @return the value
	 */
	static int intRight(int val, long amt) {
		if (Long.compareUnsigned(amt, Integer.SIZE) >= 0) {
			return 0;
		}
		return val >>> amt;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_RIGHT int_right} on JVM ints.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register.
	 * 
	 * @param val the value as in {@code val >> amt}
	 * @param amt the amt as in {@code val >> amt}
	 * @return the value
	 */
	static int intRight(int val, int amt) {
		if (Long.compareUnsigned(amt, Integer.SIZE) >= 0) {
			return 0;
		}
		return val >>> amt;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_RIGHT int_sright} on multi-precision ints.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHR ishr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size fill the register with
	 * the sign bit.
	 * 
	 * @param out the array to receive the output, in little-endian order
	 * @param outBytes the actual size in bytes of the output operand
	 * @param val the value as in {@code val s>> amt}, in little-endian order
	 * @param amt the amt as in {@code val s>> amt}, in little-endian order
	 */
	static void intSRight(int[] out, int outBytes, int[] val, int[] amt) {
		int sign = val[val.length - 1] < 0 ? -1 : 0;
		if (Integer.compareUnsigned(amt[0], outBytes * Byte.SIZE) >= 0) {
			Arrays.fill(out, sign);
			return;
		}
		for (int i = 1; i < amt.length; i++) {
			if (amt[i] != 0) {
				Arrays.fill(out, sign);
				return;
			}
		}
		MpShiftPrivate.sshr(out, val, amt[0], sign);
	}

	/**
	 * The implementation of {@link PcodeOp#INT_SRIGHT int_sright} on JVM longs.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHR ishr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size fill the register with
	 * the sign bit.
	 * 
	 * @param val the value as in {@code val s>> amt}
	 * @param amt the amt as in {@code val s>> amt}
	 * @return the value
	 */
	static long intSRight(long val, long amt) {
		if (Long.compareUnsigned(amt, Long.SIZE) >= 0) {
			return val >> (Long.SIZE - 1);
		}
		return val >> amt;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_SRIGHT int_sright} on JVM long with int amt.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHR ishr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size fill the register with
	 * the sign bit.
	 * 
	 * @param val the value as in {@code val s>> amt}
	 * @param amt the amt as in {@code val s>> amt}
	 * @return the value
	 */
	static long intSRight(long val, int amt) {
		if (Integer.compareUnsigned(amt, Long.SIZE) >= 0) {
			return val >> (Long.SIZE - 1);
		}
		return val >> amt;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_SRIGHT int_sright} on JVM int with long amt.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHR ishr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size fill the register with
	 * the sign bit.
	 * 
	 * @param val the value as in {@code val s>> amt}
	 * @param amt the amt as in {@code val s>> amt}
	 * @return the value
	 */
	static int intSRight(int val, long amt) {
		if (Long.compareUnsigned(amt, Integer.SIZE) >= 0) {
			return val >> (Integer.SIZE - 1);
		}
		return val >> amt;
	}

	/**
	 * The implementation of {@link PcodeOp#INT_SRIGHT int_sright} on JVM ints.
	 * 
	 * <p>
	 * The semantics here are subtly different than the JVM's {@link Opcodes#ISHR ishr}: 1) The
	 * amount must be treated as unsigned. 2) Shifts in excess of val's size fill the register with
	 * the sign bit.
	 * 
	 * @param val the value as in {@code val s>> amt}
	 * @param amt the amt as in {@code val s>> amt}
	 * @return the value
	 */
	static int intSRight(int val, int amt) {
		if (Long.compareUnsigned(amt, Integer.SIZE) >= 0) {
			return val >> (Integer.SIZE - 1);
		}
		return val >> amt;
	}

	static final long MASK_I2UL = 0x0000_0000_ffff_ffffL;

	/**
	 * The implementation of {@link PcodeOp#INT_MULT} on mp-ints.
	 * <p>
	 * All arrays are in little-endian order
	 * 
	 * @param out the array allocated to receive the output
	 * @param inL the array of left input legs
	 * @param inR the array of right input legs
	 */
	static void mpIntMultiply(int[] out, int[] inL, int[] inR) {
		long carry = 0;
		long rp = inR[0] & MASK_I2UL;
		for (int li = 0; li < inL.length && li < out.length; li++) {
			long lp = inL[li] & MASK_I2UL;
			carry += lp * rp;
			out[li] = (int) carry;
			carry >>>= Integer.SIZE;
		}

		for (int ri = 1; ri < inR.length && ri < out.length; ri++) {
			carry = 0;
			rp = inR[ri] & MASK_I2UL;
			for (int li = 0; li < inL.length && ri + li < out.length; li++) {
				long lp = inL[li] & MASK_I2UL;
				long op = out[li + ri] & MASK_I2UL;
				carry += op + lp * rp;
				out[li + ri] = (int) carry;
				carry >>>= Integer.SIZE;
			}
		}
	}

	public static String mpToString(int[] legs) {
		if (legs == null) {
			return "null";
		}
		List<String> list = IntStream.of(legs).mapToObj(i -> "%08x".formatted(i)).toList();
		return list.reversed().stream().collect(Collectors.joining(":"));
	}

	enum MpDivPrivate {
		;

		/**
		 * Count the number of leading 0 bits in the first non-zero leg, and identify that leg's
		 * index
		 * 
		 * @param legs
		 */
		static int lz(int[] legs) {
			// Least-significant leg is first
			int count = 0;
			for (int i = legs.length - 1; i >= 0; i--) {
				int llz = Integer.numberOfLeadingZeros(legs[i]);
				count += llz;
				if (llz != Integer.SIZE) {
					break;
				}
			}
			return count;
		}

		static int size(int[] legs) {
			// Least-significant leg is first
			for (int i = legs.length - 1; i >= 0; i--) {
				if (legs[i] != 0) {
					return i + 1;
				}
			}
			return 0;
		}

		/**
		 * Shift the given mp-int legs left the given number of bits
		 * 
		 * @param legs the legs
		 * @param shift the number of bits to shift left
		 */
		static void shl(int[] legs, int shift) {
			if (shift == 0) {
				return; // The extra leading leg is already 0
			}
			assert shift >= 0 && shift < Integer.SIZE;

			long carry = 0;
			for (int i = 0; i < legs.length; i++) {
				carry |= (legs[i] & MASK_I2UL) << shift;
				legs[i] = (int) carry;
				carry >>>= Integer.SIZE;
			}
		}

		static void shr(int[] legs, int shift) {
			if (shift == 0) {
				return;
			}
			assert shift >= 0 && shift < Integer.SIZE;

			long carry = 0;
			for (int i = legs.length - 1; i >= 0; i--) {
				carry |= (legs[i] & MASK_I2UL) << (Integer.SIZE - shift);
				legs[i] = (int) (carry >> Integer.SIZE);
				carry <<= Integer.SIZE;
			}
		}

		/**
		 * Perform unsigned division for a multi-precision dividend and single-precision divisor
		 * 
		 * @param out the output for the quotient
		 * @param inL the dividend, and the output for the remainder
		 * @param sizeL the number of legs in the dividend
		 * @param inR the divisor
		 */
		static void divideMpSp(int[] out, int[] inL, int sizeL, int inR) {
			long r = 0;
			for (int j = sizeL - 1; j >= 0; j--) {
				r <<= Integer.SIZE;
				r += inL[j] & MASK_I2UL;
				out[j] = (int) (r / inR);
				r %= inR;
				inL[j] = 0; // So that the mp-int inL is truly the remainder
			}
			inL[0] = (int) r;
		}

		/**
		 * Perform unsigned division (or division of magnitudes)
		 * 
		 * @param out the output for the quotient
		 * @param inL the dividend, and the output for the remainder
		 * @param inR the divisor
		 * @implNote this is just Algorithm D from Knuth's TAOCP Volume 2 without any sophisticated
		 *           optimizations. We don't really need to optimize for the "big" case, we just
		 *           need to support the bigger-than-a-machine-word case.
		 */
		static void divide(int[] out, int[] inL, int[] inR) {
			/**
			 * Before we mutate anything, compute lengths for D2. We'll compute sizeR from the
			 * leading-zeroes computation in D1.
			 */
			int sizeL = size(inL);

			/**
			 * D1 [Normalize]
			 * 
			 * My understanding of this step is to assure that the divisor (inR) has a 1 in the
			 * most-significant bit of its most-significant leg ("digit" in the text's terminology).
			 */
			int shiftBits = lz(inR);

			int truncR = shiftBits / Integer.SIZE;
			int sizeR = inR.length - truncR;

			if (sizeR == 1) {
				/**
				 * Never mind all this. Just do the simple algorithm (Exercise 16, in TAOCP Vol. 2,
				 * Section 4.3.1). We actually can't use the full multi-precision algorithm, because
				 * the adjustment of qHat in step D3 assumes the size of the divisor is >= 2 legs.
				 */
				divideMpSp(out, inL, sizeL, inR[0]);
				return;
			}

			int shift = shiftBits % Integer.SIZE;

			shl(inL, shift);
			shl(inR, shift);

			/**
			 * D2 [Initialize j]
			 * 
			 * What should be an <em>easy</em> step here is complicated by the fact that every
			 * operand has to (conventionally) have equal size is Sleigh. Thus, we need to seek out
			 * the most-significant leg with a non-zero value for each, and then compute j. Probably
			 * need to avoid an off-by-one error here, too.
			 * 
			 * dividend has size m + n "sizeL" (text calls dividend u_{m+n-1}...u_0)
			 * 
			 * divisor has size n "sizeR" (text calls divisor v_{n-1}...v_0})
			 * 
			 * Thus m = sizeL - sizeR
			 */
			for (int j = sizeL - sizeR; j >= 0; j--) { // step and test are D7
				/**
				 * D3 [Calculate q\^]
				 */
				// NOTE That inL is over-provisioned by 1, so we're good to index m+n
				long qHat = (inL[sizeR + j] & MASK_I2UL) << Integer.SIZE;
				qHat |= inL[sizeR + j - 1] & MASK_I2UL;
				long rHat = qHat;
				long vNm1 = inR[sizeR - 1] & MASK_I2UL; // v_{n-1}
				qHat /= vNm1;
				rHat %= vNm1;

				do {
					if (qHat == 1L << Integer.SIZE || Long.compareUnsigned(
						qHat * (inR[sizeR - 2] & MASK_I2UL),
						(rHat << Integer.SIZE) + (inL[sizeR + j - 2] & MASK_I2UL)) > 0) {
						qHat--;
						rHat += vNm1;
					}
					else {
						break;
					}
				}
				while (Long.compareUnsigned(rHat, 1L << Integer.SIZE) < 0);

				/**
				 * D4 [Multiply and subtract]
				 * 
				 * NOTE: borrow will become -1 if a borrow is needed, so add it to each subsequent
				 * leg and use signed shift.
				 */
				long borrow = 0;
				for (int i = 0; i < sizeR - 1; i++) {
					borrow = (inL[j + i] & MASK_I2UL) - qHat * (inR[i] & MASK_I2UL) + borrow;
					inL[j + i] = (int) borrow;
					borrow >>= Integer.SIZE;
				}
				borrow = (inL[j + sizeR] & MASK_I2UL) + borrow;
				inL[j + sizeR] = (int) borrow;
				borrow >>= Integer.SIZE;

				/**
				 * D5 [Test remainder]
				 */
				if (borrow != 0) {
					assert borrow == -1;
					/**
					 * D6 [Add back]
					 */
					qHat--;

					long carry = 0;
					for (int i = 0; i < sizeR; i++) {
						carry += (inL[j + i] & MASK_I2UL) + inR[i];
						inL[j + i] = (int) carry;
						carry >>>= Integer.SIZE;
					}
				}
				out[j] = (int) qHat; // Completion of D5

				/**
				 * D7 [Loop on j]
				 * 
				 * The step and test of the for loop that ends here implements D7
				 */
			}
			/**
			 * D8 [Unnormalize]
			 */
			shr(inL, shift);
		}

		static void neg(int[] legs, int count) {
			long carry = 1;
			for (int i = 0; i < count; i++) {
				carry += (~legs[i] & MASK_I2UL);
				legs[i] = (int) carry;
				carry >>>= Integer.SIZE;
			}
		}

		static void sdivide(int[] out, int[] inL, int[] inR) {
			// NOTE: inL is over-provisioned by 1
			boolean signL = inL[inL.length - 2] < 0;
			boolean signR = inR[inR.length - 1] < 0;
			if (signL) {
				neg(inL, inL.length - 1);
			}
			if (signR) {
				neg(inR, inR.length);
			}
			divide(out, inL, inR);
			if (signL != signR) {
				neg(out, out.length);
			}
			if (signL) {
				neg(inL, inL.length - 1);
			}
		}
	}

	/**
	 * The implementation of {@link PcodeOp#INT_DIV} on mp-ints.
	 * <p>
	 * All arrays are in little-endian order. While this directly implements
	 * {@link PcodeOp#INT_DIV}, it is also used for {@link PcodeOp#INT_REM},
	 * {@link PcodeOp#INT_SDIV}, and {@link PcodeOp#INT_SREM}.
	 * 
	 * @param out the array allocated to receive the quotient
	 * @param inL the array of dividend input legs, over-provisioned by 1, which will also receive
	 *            the remainder
	 * @param inR the array of divisor input legs
	 */
	static void mpIntDivide(int[] out, int[] inL, int[] inR) {
		MpDivPrivate.divide(out, inL, inR);
	}

	/**
	 * The implementation of {@link PcodeOp#INT_SDIV} on mp-ints.
	 * <p>
	 * All arrays are in little-endian order. While this directly implements
	 * {@link PcodeOp#INT_SDIV}, it is also used for {@link PcodeOp#INT_SREM}.
	 * 
	 * @param out the array allocated to receive the quotient
	 * @param inL the array of dividend input legs, over-provisioned by 1, which will also receive
	 *            the remainder
	 * @param inR the array of divisor input legs
	 */
	static void mpIntSignedDivide(int[] out, int[] inL, int[] inR) {
		MpDivPrivate.sdivide(out, inL, inR);
	}

	/**
	 * Get the language for the given string language ID
	 * 
	 * <p>
	 * This is called by generated static initializers.
	 * 
	 * @param languageID the language ID
	 * @return the language
	 * @throws LanguageNotFoundException if the language is not found
	 */
	static Language getLanguage(String languageID) throws LanguageNotFoundException {
		return DefaultLanguageService.getLanguageService().getLanguage(new LanguageID(languageID));
	}

	/**
	 * Construct a contextreg value from the given language and hex value
	 * 
	 * <p>
	 * This is called by generated static initializers to pre-construct context values.
	 * 
	 * @param language the language
	 * @param value the value as a string of hexadecimal digits
	 * @return the value
	 */
	static RegisterValue createContext(Language language, String value) {
		return new RegisterValue(language.getContextBaseRegister(),
			new BigInteger(value, 16));
	}

	/**
	 * Construct a varnode
	 * 
	 * <p>
	 * This is called by generated static initializers to pre-construct any varnodes it needs to
	 * re-create, mostly for invoking userops with the Standard strategy.
	 * 
	 * @param factory the language's address factory
	 * @param space the name of the space
	 * @param offset the byte offset
	 * @param size the size (in bytes)
	 * @return the varnode
	 */
	static Varnode createVarnode(AddressFactory factory, String space, long offset, int size) {
		return new Varnode(factory.getAddressSpace(space).getAddress(offset), size);
	}

	/**
	 * Get this instance's bound thread.
	 * 
	 * <p>
	 * The generator implements a standard getter. This is frequently used by other default methods
	 * of this interface, which are in turn invoked by generated code.
	 * 
	 * @return the thread
	 */
	JitPcodeThread thread();

	/**
	 * Set the bound thread's program counter and decode context.
	 * 
	 * <p>
	 * This is called during retirement, i.e., upon exiting a passage. This just converts things to
	 * the right type and invokes
	 * {@link JitPcodeThread#writeCounterAndContext(Address, RegisterValue)}.
	 * 
	 * @param counter the offset of the next instruction to execute
	 * @param context the decode context for the next instruction
	 */
	default void writeCounterAndContext(long counter, RegisterValue context) {
		JitPcodeThread thread = thread();
		Address pc = thread.getLanguage().getDefaultSpace().getAddress(counter);
		thread.writeCounterAndContext(pc, context);
	}

	/**
	 * Set the bound thread's program counter and decode context, without writing it to the machine
	 * state.
	 * 
	 * <p>
	 * This is called during retirement upon entering a hazard. This just converts things to the
	 * right type and invokes {@link JitPcodeThread#setCounterAndContext(Address, RegisterValue)}.
	 * 
	 * @param counter the offset of the next instruction to execute
	 * @param context the decode context for the next instruction
	 */
	default void setCounterAndContext(long counter, RegisterValue context) {
		JitPcodeThread thread = thread();
		Address pc = thread.getLanguage().getDefaultSpace().getAddress(counter);
		thread.setCounterAndContext(pc, context);
	}

	/**
	 * Retrieve a userop definition from the bound thread.
	 * 
	 * <p>
	 * This is invoked by generated constructors to retain a userop reference for later invocation.
	 * Note that it is the userop as defined by the user or emulator, not any wrapper used during
	 * decode or translation. Depending on the invocation strategy, this reference may be saved and
	 * later used with {@link #invokeUserop(PcodeUseropDefinition, Varnode, Varnode[])}, or its
	 * method and instance may be extracted and saved for Direct invocation later.
	 * 
	 * @param name the name of the userop
	 * @return the userop or {@code null}
	 * @see JitDataFlowUseropLibrary
	 */
	default PcodeUseropDefinition<byte[]> getUseropDefinition(String name) {
		return thread().getUseropLibrary().getUserops().get(name);
	}

	/**
	 * Invoke the given userop on the bound thread with the given operands
	 * 
	 * <p>
	 * This is invoked by generated code in {@link JitCompiledPassage#run(int)} to invoke a userop
	 * via the Standard strategy.
	 * 
	 * @param userop the userop definition
	 * @param output an optional output operand
	 * @param inputs the input operands
	 * @see JitDataFlowUseropLibrary
	 * @see PcodeUseropDefinition#execute(PcodeExecutor, PcodeUseropLibrary, Varnode, List)
	 */
	default void invokeUserop(PcodeUseropDefinition<byte[]> userop, Varnode output,
			Varnode[] inputs) {
		userop.execute(thread().getExecutor(), thread().getUseropLibrary(), output,
			Arrays.asList(inputs));
	}

	/**
	 * Construct an exception when attempting to execute an "instruction" that could not be decoded.
	 * 
	 * <p>
	 * When the decoder encounters an error, instead of crashing immediately, it must consider that
	 * execution may not actually reach the error, so it instead emits pseudo-instructions
	 * describing the error. The translator then emits code that will invoke this method and throw
	 * the result. Thus, we only crash if the erroneous condition is actually met.
	 * 
	 * @param message the human-readable message
	 * @param counter the program counter where the decode error was encountered
	 * @return the exception, which should be thrown immediately
	 */
	default DecodePcodeExecutionException createDecodeError(String message, long counter) {
		return new DecodePcodeExecutionException(message,
			thread().getLanguage().getDefaultSpace().getAddress(counter));
	}

	/**
	 * Construct an exit slot for the given branch target
	 * 
	 * <p>
	 * This is invoked by generated constructors for each branch target that exits the passage. Each
	 * is saved as a field and will be filled lazily with its chained entry point the first time the
	 * branch is taken.
	 * 
	 * @param target the target program counter
	 * @param ctx the target decode context
	 * @return the exit slot
	 */
	default ExitSlot createExitSlot(long target, RegisterValue ctx) {
		return new ExitSlot(thread(), target, ctx);
	}

	/**
	 * Get the chained entry point for the given exit point's target
	 * 
	 * <p>
	 * This is invoked by generated code in {@link JitCompiledPassage#run(int)} to take a branch
	 * exiting the passage. The first time, the exit slot is lazily filled, possibly requiring
	 * further JIT translation.
	 * 
	 * @param slot the slot for the target of the branch we're taking
	 * @return the chained entry point
	 */
	static EntryPoint getChained(ExitSlot slot) {
		return slot.getChained();
	}

	/**
	 * Invoke {@link JitPcodeThread#count(int, int)} for the bound thread
	 * 
	 * @param instructions as in {@link JitPcodeThread#count(int, int)}
	 * @param trailingOps as in {@link JitPcodeThread#count(int, int)}
	 */
	default void count(int instructions, int trailingOps) {
		thread().count(instructions, trailingOps);
	}
}
